﻿using EasyCore.App;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using NPOI.SS.Formula.Functions;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using IdentityModel;
using EasyCore.Authentications.Dtos;
using EasyCore.Authentications;
using System.Management;
using EasyCore.Extensions;
using System.Reflection.PortableExecutable;

namespace EasyCore.Auth
{
    public static class AuthenticationExtentions
    {
        /// <summary>
        /// 增加jwt认证
        /// </summary>
        /// <param name="services"></param>
        public static void AddJwtAuthentication(this IServiceCollection services)
        {
            // 使用非对称加密进行签名（推荐）
            //var rsaSecurityPrivateKeyString = File.ReadAllText(Path.Combine(Env.ContentRootPath, "Rsa", "key.private.json"));
            //var rsaSecurityPublicKeyString = File.ReadAllText(Path.Combine(Env.ContentRootPath, "Rsa", "key.public.json"));
            //RsaSecurityKey rsaSecurityPrivateKey = new(JsonConvert.DeserializeObject<RSAParameters>(rsaSecurityPrivateKeyString));
            //RsaSecurityKey rsaSecurityPublicKey = new(JsonConvert.DeserializeObject<RSAParameters>(rsaSecurityPublicKeyString));
            //services.AddSingleton(sp => new SigningCredentials(rsaSecurityPrivateKey, SecurityAlgorithms.RsaSha256Signature));

            //获得JWT配置
            var config = EasyApp.Configuration;
            services.Configure<JwtOptions>(EasyApp.Configuration.GetSection(JwtOptions.Name));
            var jwtOptions = config.Get<JwtOptions>(JwtOptions.Name);
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecretKeyStr)); //密钥
            var creds = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
            services.AddSingleton<SigningCredentials>(creds);
            services.AddTransient<AppJwtBearerEvents>();
            services.AddTransient<IAuthTokenService, AuthTokenService>();
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    // 有效的签名算法列表，仅列出的算法是有效的（强烈建议不要设置该属性）
                    // 默认 null，即所有算法均可
                    ValidAlgorithms = new[] { SecurityAlgorithms.HmacSha256, SecurityAlgorithms.RsaSha256, SecurityAlgorithms.Aes128CbcHmacSha256 },
                    // 有效的token类型
                    // 默认 null，即所有类型均可
                    ValidTypes = new[] { JwtConstants.HeaderType },

                    // 有效的签发者，当需要验证签发者时，会将其与token中的签发者进行对比
                    ValidIssuer = jwtOptions.Issuer,
                    // 可以指定多个有效的签发者
                    //ValidIssuers = new [] { jwtOptions.Issuer },
                    // 是否验证签发者
                    // 默认 true
                    ValidateIssuer = true,

                    // 有效的受众，当需要验证受众时，会将其与token中的受众进行对比
                    ValidAudience = jwtOptions.Audience,
                    // 可以指定多个有效的受众
                    ValidAudiences = new[] { jwtOptions.Audience },
                    // 是否验证受众
                    // 如果指定的值与token中的 aud 参数不匹配，则token将被拒绝
                    // 默认 true
                    ValidateAudience = true,

                    // 签发者用于token签名的密钥
                    // 对称加密，使用相同的key进行加签验签
                    IssuerSigningKey = securityKey,
                    // 非对称加密，使用私钥加签，使用公钥验签
                    //IssuerSigningKey = rsaSecurityPublicKey,
                    // 是否验证签发者用于token签名的密钥
                    // 默认 false
                    ValidateIssuerSigningKey = true,

                    // 是否验证token是否在有效期内（包括nbf和exp）
                    // 默认 true
                    ValidateLifetime = true,

                    // 是否要求token必须进行签名
                    // 默认 true
                    RequireSignedTokens = true,
                    // 是否要求token必须包含过期时间
                    // 默认 true
                    RequireExpirationTime = true,

                    // 设置 HttpContext.User.Identity.NameClaimType，便于 HttpContext.User.Identity.Name 取到正确的值
                    // NameClaimType = JwtClaimTypes.Name,
                    // 设置 HttpContext.User.Identity.RoleClaimType，便于 HttpContext.User.Identity.IsInRole(xxx) 取到正确的值
                    RoleClaimType = JwtClaimTypes.Role,

                    // 时钟漂移
                    // 可以在验证token有效期时，允许一定的时间误差（如时间刚达到token中exp，但是允许未来5分钟内该token仍然有效）
                    // 默认 300s，即 5min
                    ClockSkew = TimeSpan.Zero,

                    // token解密密钥
                    //TokenDecryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Total Bytes Length At Least 256!"))
                };


                // 当token验证通过后（执行完 JwtBearerEvents.TokenValidated 后），
                // 是否将token存储在 Microsoft.AspNetCore.Authentication.AuthenticationProperties 中
                // 默认 true
                 options.SaveToken = true;


                // token验证处理器列表
                // 默认 有1个 JwtSecurityTokenHandler
                //options.SecurityTokenValidators.Clear();
                //options.SecurityTokenValidators.Add(new JwtSecurityTokenHandler());
                // 通过Post添加AppJwtSecurityTokenHandler

                // 受众，指该token是服务于哪个群体的（群体范围名），或该token所授予的有权限的资源是哪一块（资源的uri）
                // 该属性主要用于当其不为空，但 TokenValidationParameters.ValidAudience 为空时，将其赋值给 TokenValidationParameters.ValidAudience
                // 一般不使用该属性
                //options.Audience = jwtOptions.Audience;  

                options.EventsType = typeof(AppJwtBearerEvents);
            });
        }

        /// <summary>
        /// 获得令牌
        /// </summary>
        /// <param name="claimsDic"></param>
        /// <returns></returns>
        public static string GenerateToken(this Dictionary<string, string> claimsDic)
        {
            //获得JWT配置
            var config = EasyApp.Configuration;
            var jwtSettings = config.Get<JwtOptions>(JwtOptions.Name);

            //生成声明
            var claims = new List<Claim>();
            foreach (var key in claimsDic.Keys)
            {
                claims.Add(new Claim(key, claimsDic[key]));
            }

            //创建签名证书
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKeyStr));
            var creds = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            //令牌描述器
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                IssuedAt = DateTime.UtcNow,
                Issuer = jwtSettings.Issuer, //发行者
                Audience = jwtSettings.Audience, //接收者
                Subject = new ClaimsIdentity(claims.ToArray()),
                Expires = DateTime.UtcNow.AddMinutes(jwtSettings.AccessTokenExpiresMinutes), //过期时间
                SigningCredentials = creds
            };

            //处理器及生成token
            var tokenHandler = new JwtSecurityTokenHandler();
            var token = tokenHandler.CreateToken(tokenDescriptor); //生成token             
            var tokenStr = tokenHandler.WriteToken(token); //token转换成字符串
            return tokenStr;
        }
    }
}
